package mobileMoney;

import sim.engine.SimState;
import sim.engine.Steppable;
import sim.field.continuous.Continuous2D;
import sim.field.network.Edge;
import sim.util.*;

public class Client implements Steppable {
	/**
	 * 
	 */
	public static final double MAX_FORCE = 3.0;
	double balance = 0; // balance of transactions
	String currency = "SEK";
	int numTransactions = 0;
	int numDeposits = 0;
	int numWithdraws = 0;
	int numTransfers = 0;
	int age=1; // 1=young, 2=adult, 3= senior
	public String getCurrency() {
		return currency;
	}

	public void setCurrency(String currency) {
		this.currency = currency;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	boolean fraudster = false;

	public boolean isFraudster() {
		return fraudster;
	}

	public void setFraudster(boolean fraudster) {
		this.fraudster = fraudster;
	}

	Profile profile;

	public boolean isActive() {
		return active;
	}

	public void setActive(boolean active) {
		this.active = active;
	}

	int location = 0;
	boolean active = true;

	public int getLocation() {
		return location;
	}

	public void setLocation(int location) {
		this.location = location;
	}

	public int getNumTransactions() {
		return numTransactions;
	}

	public void setNumTransactions(int numTransactions) {
		this.numTransactions = numTransactions;
	}

	public int getNumDeposits() {
		return numDeposits;
	}

	public void setNumDeposits(int numDeposits) {
		this.numDeposits = numDeposits;
	}

	public int getNumWithdraws() {
		return numWithdraws;
	}

	public void setNumWithdraws(int numWithdraws) {
		this.numWithdraws = numWithdraws;
	}

	public int getNumTransfers() {
		return numTransfers;
	}

	public void setNumTransfers(int numTransfers) {
		this.numTransfers = numTransfers;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

	protected boolean checkDeposit(double amount) {
		if (this.profile.getMaxBalance() >= this.getBalance() + amount
				&& this.profile.getTransactionLimit() >= amount) {
			return true;
		} else {
			return false;
		}
	}

	public boolean deposit(double amount) {
		if (checkDeposit(amount)) {
			this.balance += amount;
			this.numDeposits++;
			return true;
		} else {
			return false;
		}
	}

	protected boolean checkWithdraw(double amount) {
		if (balance >= amount && this.profile.getDayLimit() >= amount) {
			return true;
		} else
			return false;
	}

	public boolean withdraw(double amount) {
		if (checkWithdraw(amount)) {
			this.balance -= amount;
			this.numWithdraws++;
			return true;
		} else {
			return false;
		}
	}

	private static final long serialVersionUID = 1L;

	public void upgradeClient() {
		if (this.profile.id == 1) {
			Profile p2 = new Profile(2, 35000, 35000, 35000, true, 35000);
			this.setProfile(p2);
		}
	}

	public void evaluateStatus(double prob) {
		double probClosingAccount = 0.00001; // prob of closing account
		double cumProbUpgrade = 0.0005;// cumulative probability of upgrading
										// account is
		if (profile.id == 1) {
			if (prob < probClosingAccount) {
				this.active = false;
			} else if (prob < cumProbUpgrade) {
				upgradeClient();
			}
		} else if (profile.id == 2) {
			if (prob < probClosingAccount) {
				this.active = false;
			}
		}
	}

	public int evaluateTransaction(double prob) {
		int result = 0; // 0 no transaction, 1=deposit, 2=withdraw, 3=
						// transference
		double perBalance = this.balance / this.profile.maxBalance;
		if (perBalance < 0.15) {
			if (prob < 0.05) {
				result = 2;
			} else if (prob < 0.1) {
				result = 3;
			} else if (prob < 0.5) {
				result = 1;
			}
		} else if (perBalance < 0.8) {
			if (prob < 0.1) {
				result = 1;
			} else if (prob < 0.3) {
				result = 2;
			} else if (prob < 0.6) {
				result = 3;
			}
		} else {
			if (prob < 0.001) {
				result = 1;
			} else if (prob < 0.4) {
				result = 2;
			} else if (prob < 0.7) {
				result = 3;
			}
		}
		return result;
	}

	public double evaluateAmount(TransactionType operation, Clients clients,
			double limit) {
		double amount = 0; // depends on transaction and age of customer
		double factorAge = 1;
		if (age == 1) {
			factorAge = 0.5;
		} else if (age == 2) {
			factorAge = 0.8;
		}
		if (limit > operation.maxVal) { // maximum between limit and max of
										// operation
			limit = operation.maxVal;
		}
		if (limit >= 1) {
			amount = clients.random.nextLong((long) limit) * factorAge
					+ clients.random.nextDouble();
		}
		return amount;
	}

	public void step(SimState state) {
		Clients clients = (Clients) state;
		if (active) {

			Continuous2D land = clients.land;
			Double2D me = clients.land.getObjectLocation(this);
			MutableDouble2D newLoc = new MutableDouble2D();
			// Go through my buddies and determine how much I want to be near
			// them
			MutableDouble2D forceVector = new MutableDouble2D();
			Bag out = clients.contacts.getEdgesOut(this);

			int len = out.size();
			// loop the contacts
			int contact = 0;
			Client clientE = new Client();

			if (len > 0) {
				contact = clients.random.nextInt(len);
			} else {
				contact = 0;
			}
			Edge e = (Edge) (out.get(contact));
			Client clientB = (Client) (e.getOtherNode(this));

			// probability to make a transaction
			evaluateStatus(clients.random.nextDouble());

			if (clients.transactionRate < clients.random.nextDouble()) {

				double amount = 0;
				// select one transaction
				int numOp = clients.random.nextInt(100);
				TransactionType operation = clients.getTypes().get(numOp);
				while (operation.maxVal > this.balance && !operation.nature) {
					operation = clients.getTypes().get(
							clients.random.nextInt(100));
				}

				this.numTransactions++;

				if (operation.nature) {
					// true = deposit

					amount = clients.random.nextLong((long) operation.maxVal)
							+ clients.random.nextDouble();

					if (this.deposit(amount)) {
						clients.getTrans().add(
								new Transaction(clients.schedule.getSteps(),
										this, null, (short) operation
												.getTypeCode(), amount, ""));
					}
				} else { // false = withdraw

					amount = clients.random.nextLong((long) operation.maxVal)
							+ clients.random.nextDouble();

				}
				// probability for a transfer when is withdraw
				if (clients.random.nextBoolean(0.9) && len > 0) {

					this.numTransfers++;
					// only when is possible to deposit
					if (clientB.checkDeposit(amount)) {
						if (this.withdraw(amount)) {
							clients.getTrans()
									.add(new Transaction(clients.schedule
											.getSteps(), this, clientB,
											(short) operation.getTypeCode(),
											amount, ""));
							clientB.deposit(amount);
							clients.getTrans().add(
									new Transaction(
											clients.schedule.getSteps(),
											clientB, this, (short) operation
													.getTypeCode(), amount,
											" deposited from B "));
						}
					}

				} else {
					if (this.withdraw(amount)) {
						clients.getTrans().add(
								new Transaction(clients.schedule.getSteps(),
										this, clientE, (short) operation
												.getTypeCode(), amount, ""));
					}
				}
			}

			// move the agents
			double lx = clients.randomMultiplier
					* (clients.random.nextDouble() * 1.0 - 0.5);
			if (lx > 100)
				lx = 99;
			double ly = clients.randomMultiplier
					* (clients.random.nextDouble() * 1.0 - 0.5);
			if (ly > 100)
				ly = 99;

			newLoc.addIn(new Double2D(lx, ly));
			newLoc.addIn(me);
			clients.land.setObjectLocation(this, new Double2D(newLoc));

		}

	}

	public Profile getProfile() {
		return profile;
	}

	public void setProfile(Profile profile) {
		this.profile = profile;
	}

	public String toString() {
		return "C" + Integer.toString(this.hashCode());
	}
}
